From 1ab410621c9bf6b0048f2788257919ca4652cdf7 Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Thu, 16 Apr 2026 08:26:01 +0200 Subject: [PATCH] perf: delete_category uses single API session (v0.4.13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously delete_category made two separate login cycles (accgetallfamily to verify + taskcategorydelete to delete) = 6 HTTP calls. Now both happen in one session = 4 HTTP calls. Halves latency and eliminates the risk of a compounding timeout when the API is temporarily slow. FW_DEBUG=1 investigation confirmed the API itself is fast (<1s); the reported 4-min timeout was transient API slowness amplified by the double session overhead. Also recreated 'Obst & Gemüse' (emoji 🍎) as taskCategory/23431854_4956806 after it was accidentally consumed by the debug script. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 2 +- pyproject.toml | 2 +- src/mcp_familywall/__init__.py | 2 +- src/mcp_familywall/server.py | 69 +++++++++++++++++++++------------- 4 files changed, 45 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 6cbf513..2c99ad9 100644 --- a/README.md +++ b/README.md @@ -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.12) +## Features (v0.4.13) ### Read diff --git a/pyproject.toml b/pyproject.toml index 50a29a7..343479d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.4.12" +version = "0.4.13" description = "MCP server for Family Wall — read your family's lists and tasks via Claude" readme = "README.md" requires-python = ">=3.12" diff --git a/src/mcp_familywall/__init__.py b/src/mcp_familywall/__init__.py index 9b084a6..4b2ce7d 100644 --- a/src/mcp_familywall/__init__.py +++ b/src/mcp_familywall/__init__.py @@ -1 +1 @@ -__version__ = "0.4.12" +__version__ = "0.4.13" diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index 55b7407..9c91dd0 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -502,42 +502,57 @@ def delete_category(category_id: str) -> str: 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. + # Safety check + delete in a SINGLE session to minimise API round-trips. + # Previously two separate sessions were used (accgetallfamily + taskcategorydelete), + # causing 6 HTTP calls. One session = 4 HTTP calls (login, check, delete, logout). try: - data = _accgetallfamily() + email, password = get_credentials() except RuntimeError as exc: return f"Error: {exc}" + cat_obj: dict[str, Any] | None = None try: - raw_cats: list[dict[str, Any]] = data["a01"]["r"]["r"]["updatedCreated"] - except (KeyError, TypeError): - raw_cats = [] + with FamilyWallClient() as client: + client.login(email, password) - cat_obj: dict[str, Any] | None = next( - (c for c in raw_cats if c.get("metaId") == category_id), None - ) + # Fetch categories and verify the target is custom (canDelete=true). + data = client.call( + "accgetallfamily", + {"a01call": "taskcategorysync", "a02call": "tasksync"}, + ) + try: + raw_cats: list[dict[str, Any]] = data["a01"]["r"]["r"]["updatedCreated"] + except (KeyError, TypeError): + raw_cats = [] - if cat_obj is None: - return f"Error: Category '{category_id}' not found." + cat_obj = next( + (c for c in raw_cats if c.get("metaId") == category_id), None + ) + if cat_obj is None: + client.logout() + 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, - ) + can_delete: str | None = cat_obj.get("rights", {}).get("canDelete") + if can_delete != "true": + client.logout() + 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}" + # Verified — delete in the same session. + client.call("taskcategorydelete", {"id": category_id}) + client.logout() + except FamilyWallError as exc: + return f"Error: Family Wall API error: {exc}" + except Exception as exc: + return f"Error: Connection error: {exc}" return json.dumps( {"deleted": True, "id": category_id, "name": cat_obj.get("name")},