feat: create_category + delete_category tools (v0.4.11)
Verified via systematic FW_DEBUG=1 probing: - taskcategoryput: requires 'name'; optional 'emoji' (Unicode or string code) accepted as-is. 'listId' param has no per-list effect — categories are family-wide. - taskcategorydelete: uses 'id' param (not 'metaId'), returns r='true'. Changes: - create_category(list_id, name, icon=None): creates custom category via taskcategoryput; icon maps to 'emoji' API param - delete_category(category_id): safety check via accgetallfamily looks up rights.canDelete='true'; system categories (rights.canDelete=null) are refused with a clear error - get_categories: now exposes 'custom' bool field (rights.canDelete='true') so callers can identify deletable categories - SPEC.md: document taskcategoryput + taskcategorydelete params, responses, error formats, and system-category protection behaviour Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -23,15 +23,16 @@ eingebunden.
|
||||
|
||||
## Aktueller Stand
|
||||
|
||||
### Implementierte Tools (v0.4.10)
|
||||
### Implementierte Tools (v0.4.11)
|
||||
|
||||
| Kategorie | Tools |
|
||||
|---|---|
|
||||
| Kreise | `get_circles`, `get_members` |
|
||||
| Listen | `get_lists` |
|
||||
| Tasks (Lesen) | `get_tasks` (inkl. `category_id`), `get_categories` |
|
||||
| Tasks (Lesen) | `get_tasks` (inkl. `category_id`), `get_categories` (inkl. `custom`-Flag) |
|
||||
| Wall | `get_activities`, `like_post` |
|
||||
| Tasks (Schreiben) | `create_task` (inkl. `category_id`), `update_task` (inkl. `category_id`), `toggle_task`, `delete_task` |
|
||||
| Kategorien (Schreiben) | `create_category` (inkl. `icon`), `delete_category` (System-Kategorien geschützt) |
|
||||
|
||||
|
||||
## Roadmap
|
||||
|
||||
@@ -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.10)
|
||||
## Features (v0.4.11)
|
||||
|
||||
### 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 (includes `category_id` field)
|
||||
- `get_categories` -- list categories available for a list (locale-filtered, default: German)
|
||||
- `get_categories` -- list categories for a list (locale-filtered; `custom` flag marks user-created ones)
|
||||
- `get_activities` -- list recent wall activities (author resolved to display name)
|
||||
|
||||
### Write (with confirmation prompt)
|
||||
@@ -19,7 +19,9 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your
|
||||
- `update_task` -- update text, description, and/or category of an existing task
|
||||
- `toggle_task` -- mark a task complete or reopen it
|
||||
- `delete_task` -- permanently delete a task
|
||||
- `like_post` -- like or unlike a wall post/activity
|
||||
- `create_category` -- create a custom category for a shopping list (with optional icon)
|
||||
- `delete_category` -- delete a custom category (system categories are protected)
|
||||
- `like_post` -- like a wall post/activity
|
||||
|
||||
## Requirements
|
||||
|
||||
|
||||
@@ -331,6 +331,65 @@ mit `{"a00": {"un": {"un": {"message": "missing value in: id"}}}}` auf Top-Level
|
||||
→ wird vom fw_client fälschlich als Erfolg interpretiert. Daher ist der korrekte Parameter-Name
|
||||
kritisch.
|
||||
|
||||
### `taskcategoryput` – Kategorie erstellen
|
||||
POST https://api.familywall.com/api/taskcategoryput
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
**Body-Parameter (verifiziert via FW_DEBUG=1):**
|
||||
|
||||
| Parameter | Pflicht | Wert |
|
||||
|---|---|---|
|
||||
| `name` | ja | Kategorie-Name (beliebiger String) |
|
||||
| `emoji` | nein | Icon: Unicode-Emoji-Zeichen (z.B. `🌿`) oder beliebiger String-Code (z.B. `"FOOD"`) — wird as-is gespeichert |
|
||||
|
||||
Hinweise:
|
||||
- Die neue Kategorie wird **allen** Listen der Familie zugeordnet — es gibt keine per-Liste-Einschränkung.
|
||||
- Benutzerdefinierte Kategorien haben `systemCategoryId=null` und `rights.canDelete='true'`.
|
||||
- System-Kategorien haben `rights.canDelete=null` — API erlaubt Löschen, aber `delete_category` Tool verweigert es.
|
||||
|
||||
**Response-Struktur (verifiziert):**
|
||||
```
|
||||
a00.r.r → vollständiges Kategorie-Objekt
|
||||
.metaId → neue Kategorie-ID (z.B. "taskCategory/23431854_4956637")
|
||||
.name → Kategorie-Name
|
||||
.taskListType → "SHOPPING_LIST" (automatisch gesetzt)
|
||||
.familyId → Familien-ID
|
||||
.accountId → Account-ID des Erstellers
|
||||
.rights.canDelete → "true" (custom Kategorien)
|
||||
.rights.canUpdate → "true" (custom Kategorien)
|
||||
.emoji → gespeicherter Icon-Wert (falls übergeben)
|
||||
```
|
||||
|
||||
**Fehlerverhalten:** Ohne `name`-Parameter:
|
||||
```json
|
||||
{"a00": {"un": {"un": {"FiZClassId": "502", "message": "cat without a name ..."}}}}
|
||||
```
|
||||
|
||||
### `taskcategorydelete` – Kategorie löschen
|
||||
POST https://api.familywall.com/api/taskcategorydelete
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
**Body-Parameter (verifiziert via FW_DEBUG=1):**
|
||||
|
||||
| Parameter | Pflicht | Wert |
|
||||
|---|---|---|
|
||||
| `id` | ja | Kategorie-MetaId aus `get_categories` (**WICHTIG: `id`, nicht `metaId`!**) |
|
||||
|
||||
**Achtung:** Falscher Parameter-Name `metaId` führt zu:
|
||||
```json
|
||||
{"a00": {"un": {"un": {"FiZClassId": "502", "message": "In request, missing value in : id"}}}}
|
||||
```
|
||||
|
||||
**Response-Struktur (verifiziert):**
|
||||
```
|
||||
a00.r.r → "true" (String)
|
||||
```
|
||||
|
||||
**Wichtig – System-Kategorien:** Die API erlaubt technisch das Löschen von System-Kategorien
|
||||
(`taskCategory/<familyId>_200` etc.), entfernt sie aber nur aus der Familie — nicht global.
|
||||
Das `delete_category`-MCP-Tool verweigert dies (Schutz via `rights.canDelete`-Check).
|
||||
Erkennung: custom Kategorien haben `rights.canDelete='true'`; System-Kategorien haben `rights.canDelete=null`.
|
||||
|
||||
### `wallmood` – Wall-Post liken
|
||||
POST https://api.familywall.com/api/wallmood
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
@@ -426,4 +485,6 @@ AND `moodStarShortcut: false` AND `moodMap: {}`.
|
||||
- ~~`wallmood`: Parameter-Name `wallId`~~ → **`wall_message_id`** (verifiziert via API-Fehlermeldung)
|
||||
- ~~`wallmood`: `moodType`-Werte, Toggle vs. explizit, Response-Struktur~~ → verifiziert: idempotentes SET mit `"STAR"`, kein Toggle (siehe oben)
|
||||
- `wallmood` Unlike: Mechanismus unbekannt — Service Worker verhindert Browser-Inspektion; alle getesteten Ansätze fehlgeschlagen (siehe oben)
|
||||
- ~~`taskcreate2` / `taskupdate2`: Kategorie-Paramter-Name~~ → **`taskCategoryId`**, Wert = vollständige metaId (verifiziert)
|
||||
- ~~`taskcreate2` / `taskupdate2`: Kategorie-Paramter-Name~~ → **`taskCategoryId`**, Wert = vollständige metaId (verifiziert)
|
||||
- ~~`taskcategoryput`: Body-Parameter, Response-Struktur~~ → `name` (Pflicht), `emoji` (optional), Response = neues Kategorie-Objekt (verifiziert)
|
||||
- ~~`taskcategorydelete`: Body-Parameter~~ → **`id`** (nicht `metaId`!), Response = `"true"` (verifiziert)
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mcp-familywall"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
@@ -402,12 +402,145 @@ def get_categories(list_id: str, locale: str = "de") -> str:
|
||||
|
||||
matched.sort(key=lambda t: t[0])
|
||||
result = [
|
||||
{"id": cat.get("metaId"), "name": cat.get("name"), "emoji": cat.get("emoji")}
|
||||
{
|
||||
"id": cat.get("metaId"),
|
||||
"name": cat.get("name"),
|
||||
"emoji": cat.get("emoji"),
|
||||
# custom=True means the category was created by the family and can be
|
||||
# deleted with delete_category. System categories have no canDelete right.
|
||||
"custom": cat.get("rights", {}).get("canDelete") == "true",
|
||||
}
|
||||
for _, cat in matched
|
||||
]
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: create_category
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def create_category(list_id: str, name: str, icon: str | None = None) -> str:
|
||||
"""Create a new custom category for a shopping list.
|
||||
|
||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||
|
||||
Custom categories appear alongside system categories when assigning
|
||||
categories to tasks via ``create_task`` or ``update_task``. They can
|
||||
later be removed with ``delete_category``.
|
||||
|
||||
Note: although ``list_id`` is accepted for context, the Family Wall API
|
||||
assigns new categories to all lists in the family — there is no
|
||||
per-list restriction.
|
||||
|
||||
Args:
|
||||
list_id: Target list ID from get_lists (e.g. ``taskList/123_456``).
|
||||
Only SHOPPING_LIST lists have categories; the parameter is
|
||||
accepted for user context but does not restrict category scope.
|
||||
name: Display name for the new category (e.g. ``"Bio-Produkte"``).
|
||||
icon: Optional icon for the category. Pass a Unicode emoji character
|
||||
(e.g. ``"🌿"``) or any short string identifier. When omitted the
|
||||
category has no icon.
|
||||
|
||||
Returns:
|
||||
JSON with the new category's ``id`` and ``name`` on success, or an
|
||||
error message.
|
||||
"""
|
||||
params: dict[str, Any] = {"name": name}
|
||||
if icon:
|
||||
params["emoji"] = icon
|
||||
|
||||
try:
|
||||
data = _authenticated_call("taskcategoryput", params)
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
|
||||
try:
|
||||
cat_obj = data["a00"]["r"]["r"]
|
||||
meta_id: str = cat_obj["metaId"]
|
||||
except (KeyError, TypeError):
|
||||
return json.dumps(
|
||||
{"warning": "Unexpected taskcategoryput response structure", "raw": data},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
return json.dumps(
|
||||
{"created": True, "id": meta_id, "name": cat_obj.get("name", name)},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: delete_category
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def delete_category(category_id: str) -> str:
|
||||
"""Permanently delete a custom category.
|
||||
|
||||
IMPORTANT: Ask the user for confirmation before calling this tool.
|
||||
|
||||
Only custom (user-created) categories can be deleted. System categories
|
||||
supplied by Family Wall (identified by ``custom=false`` in
|
||||
``get_categories`` output) are protected and this tool will refuse to
|
||||
delete them.
|
||||
|
||||
Args:
|
||||
category_id: Category metaId from get_categories
|
||||
(e.g. ``taskCategory/23431854_4956637``). Must be a custom
|
||||
category (``custom=true`` in get_categories output).
|
||||
|
||||
Returns:
|
||||
JSON success indicator or an error message.
|
||||
"""
|
||||
# Safety check: look up the category and verify it is custom (canDelete=true).
|
||||
# This prevents accidental deletion of shared system categories.
|
||||
try:
|
||||
data = _accgetallfamily()
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
|
||||
try:
|
||||
raw_cats: list[dict[str, Any]] = data["a01"]["r"]["r"]["updatedCreated"]
|
||||
except (KeyError, TypeError):
|
||||
raw_cats = []
|
||||
|
||||
cat_obj: dict[str, Any] | None = next(
|
||||
(c for c in raw_cats if c.get("metaId") == category_id), None
|
||||
)
|
||||
|
||||
if cat_obj is None:
|
||||
return f"Error: Category '{category_id}' not found."
|
||||
|
||||
can_delete: str | None = cat_obj.get("rights", {}).get("canDelete")
|
||||
if can_delete != "true":
|
||||
return json.dumps(
|
||||
{
|
||||
"error": "System categories cannot be deleted.",
|
||||
"id": category_id,
|
||||
"name": cat_obj.get("name"),
|
||||
"hint": "Only custom categories (custom=true in get_categories) can be deleted.",
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
try:
|
||||
_authenticated_call("taskcategorydelete", {"id": category_id})
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
|
||||
return json.dumps(
|
||||
{"deleted": True, "id": category_id, "name": cat_obj.get("name")},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tool: get_activities
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user