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
|
## Aktueller Stand
|
||||||
|
|
||||||
### Implementierte Tools (v0.4.10)
|
### Implementierte Tools (v0.4.11)
|
||||||
|
|
||||||
| Kategorie | Tools |
|
| Kategorie | Tools |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Kreise | `get_circles`, `get_members` |
|
| Kreise | `get_circles`, `get_members` |
|
||||||
| Listen | `get_lists` |
|
| 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` |
|
| Wall | `get_activities`, `like_post` |
|
||||||
| Tasks (Schreiben) | `create_task` (inkl. `category_id`), `update_task` (inkl. `category_id`), `toggle_task`, `delete_task` |
|
| 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
|
## 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.
|
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
|
### 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 (includes `category_id` field)
|
- `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)
|
- `get_activities` -- list recent wall activities (author resolved to display name)
|
||||||
|
|
||||||
### Write (with confirmation prompt)
|
### 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
|
- `update_task` -- update text, description, and/or category of an existing task
|
||||||
- `toggle_task` -- mark a task complete or reopen it
|
- `toggle_task` -- mark a task complete or reopen it
|
||||||
- `delete_task` -- permanently delete a task
|
- `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
|
## 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
|
→ wird vom fw_client fälschlich als Erfolg interpretiert. Daher ist der korrekte Parameter-Name
|
||||||
kritisch.
|
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
|
### `wallmood` – Wall-Post liken
|
||||||
POST https://api.familywall.com/api/wallmood
|
POST https://api.familywall.com/api/wallmood
|
||||||
Content-Type: application/x-www-form-urlencoded
|
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`: 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`: `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)
|
- `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]
|
[project]
|
||||||
name = "mcp-familywall"
|
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"
|
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"
|
||||||
|
|||||||
@@ -402,12 +402,145 @@ def get_categories(list_id: str, locale: str = "de") -> str:
|
|||||||
|
|
||||||
matched.sort(key=lambda t: t[0])
|
matched.sort(key=lambda t: t[0])
|
||||||
result = [
|
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
|
for _, cat in matched
|
||||||
]
|
]
|
||||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
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
|
# Tool: get_activities
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user