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:
@@ -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