diff --git a/CLAUDE.md b/CLAUDE.md index c1bdf89..71a5046 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden. ## Aktueller Stand -### Implementierte Tools (v0.7.3) +### Implementierte Tools (v0.7.4) | Kategorie | Tools | |---|---| @@ -34,7 +34,7 @@ und wird in Claude Desktop eingebunden. | Kategorien | `create_category`, `delete_category` | | Aktivitäten | `like_post` | | Rezepte | `get_recipes`, `get_recipe`, `create_recipe`, `update_recipe`, `delete_recipe` | -| Kreise | `create_circle`, `delete_circle`, `add_member_to_circle` | +| Kreise | `create_circle`, `update_circle`, `delete_circle`, `add_member_to_circle` | ## Roadmap @@ -48,8 +48,9 @@ und wird in Claude Desktop eingebunden. - v0.7.0: create_circle + add_member_to_circle ✓ - v0.7.1: get_lists scope fix + create_list circle_id + delete_list scope ✓ - v0.7.2: delete_circle ✓ -- v0.7.3: update_list (Umbenennen, emoji/color ändern) ✓ ← aktuell -- v0.7.4: mpadditemtolist (Zutaten → Einkaufsliste) +- v0.7.3: update_list (Umbenennen, emoji/color ändern) ✓ +- v0.7.4: update_circle (Kreis umbenennen) ✓ ← aktuell +- v0.7.5: mpadditemtolist (Zutaten → Einkaufsliste) - v0.8.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich) - v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren) @@ -148,7 +149,7 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level: | `metasync` (Rezepte lesen) | `id="recipe"` | liefert `a00.r.r.updatedCreated[]` | | `acccreatefamily` | `name` | liefert numerische Kreis-ID als String in `a00.r.r` | | `accinvite` | `familyId`, `identifier`, `role="Unknown"`, `firstname` | nur für neue FW-Accounts | -| `accupdatefamily` | `name` | aktualisiert immer den PRIMARY Kreis (ignoriert `id`/`familyId`) | +| `accupdatefamily` | `name`, `scope` | `scope`: Kreis-metaId; ohne scope → PRIMARY Kreis; erster Buchstabe wird kapitalisiert | | `adminwipefamily` | `scope` | Kreis-metaId; löscht Kreis + alle Inhalte; `a00.r.r="true"` bei Erfolg | ### Self-Like-Restriction diff --git a/README.md b/README.md index 64f2a0f..f4f6d74 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, tasks, and recipes directly from Claude. -## Features (v0.7.3) +## Features (v0.7.4) ### Read @@ -31,6 +31,7 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your - `update_recipe` -- update any field of an existing recipe (partial update — omitted fields unchanged) - `delete_recipe` -- permanently delete a recipe (only own recipes) - `create_circle` -- create a new Family Wall circle (group) +- `update_circle` -- rename a circle (server capitalises first letter; only name is changeable via API) - `delete_circle` -- permanently delete a circle and all its content (primary circle is protected) - `add_member_to_circle` -- invite a person to a circle by e-mail (for new Family Wall users only; existing accounts require in-app invitation) diff --git a/SPEC.md b/SPEC.md index 33536dd..7c492f4 100644 --- a/SPEC.md +++ b/SPEC.md @@ -611,10 +611,28 @@ POST https://api.familywall.com/api/accupdatefamily | Parameter | Pflicht | Wert | |---|---|---| | `name` | ja | Neuer Kreis-Name | +| `scope` | nein | Kreis-metaId z.B. `"family/23449644"` (ohne scope → primärer Kreis) | -**Hinweis:** Aktualisiert immer den PRIMARY Kreis des Accounts (ignoriert `id`/`familyId` Parameter). +**Response:** +``` +a00.r.r → vollständiges Kreis-Objekt + .metaId → Kreis-metaId (z.B. "family/23449644") + .name → (aktualisierter) Kreis-Name (erster Buchstabe wird kapitalisiert) + .family_id → numerische Kreis-ID (ohne "family/"-Prefix) + .members[] → Mitglieder-Liste +``` -**Verifiziert am:** 2026-04-16 via FW_DEBUG=1 +**Hinweise:** +- Ohne `scope`: aktualisiert den PRIMARY Kreis des Accounts. +- Mit `scope=family/XXXX`: aktualisiert den angegebenen Kreis (primär oder sekundär). +- Der Server kapitalisiert immer den ersten Buchstaben des Namens. +- Nur der Name ist über die API änderbar (kein Bild, keine Farbe). + +**Vorherige Dokumentation korrigiert:** Frühere SPEC-Einträge vermerkten, +dass `id`/`familyId` ignoriert werden. Mit `scope=` funktioniert die +Zielauswahl korrekt für beliebige Kreise. + +**Verifiziert am:** 2026-04-16 via FW_DEBUG=1 (scope= auf sekundärem Kreis) ### `adminwipefamily` – Kreis löschen diff --git a/pyproject.toml b/pyproject.toml index c97658c..77ea8c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.7.3" +version = "0.7.4" 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 4910b9e..ed9d4d8 100644 --- a/src/mcp_familywall/__init__.py +++ b/src/mcp_familywall/__init__.py @@ -1 +1 @@ -__version__ = "0.7.3" +__version__ = "0.7.4" diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index b09d24b..1e904b8 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -1342,6 +1342,96 @@ def create_circle(name: str) -> str: ) +# --------------------------------------------------------------------------- +# Tool: update_circle +# --------------------------------------------------------------------------- + + +@mcp.tool() +def update_circle(circle_id: str, name: str) -> str: + """Rename a Family Wall circle. + + IMPORTANT: Ask the user for confirmation before calling this tool. + + Only the circle name can be changed via the API. Note that the server + always capitalises the first letter of the new name. + + Args: + circle_id: Circle metaId from ``get_circles`` + (e.g. ``"family/23431854"``). + name: New display name for the circle. + + Returns: + JSON with ``{updated, id, name}`` on success, or an error message. + """ + if not name or not name.strip(): + return "Error: 'name' must not be empty." + + try: + email, password = get_credentials() + except RuntimeError as exc: + return f"Error: {exc}" + + try: + with FamilyWallClient() as client: + client.login(email, password) + + # Verify the circle exists before attempting the update. + circles_data = client.call("famlistfamily") + try: + raw_circles: list[dict[str, Any]] = circles_data["a00"]["r"]["r"] + if not isinstance(raw_circles, list): + raise TypeError("a00.r.r is not a list") + except (KeyError, TypeError): + client.logout() + return "Error: Unexpected famlistfamily response structure." + + target = next((c for c in raw_circles if c.get("metaId") == circle_id), None) + if target is None: + client.logout() + available = [c.get("metaId") for c in raw_circles] + return json.dumps( + { + "error": f"Circle not found: {circle_id!r}", + "available_circles": available, + }, + ensure_ascii=False, + indent=2, + ) + + # Rename the circle. + # Verified: accupdatefamily with scope= targets any circle, + # both primary and secondary. The server capitalises the first letter. + resp = client.call("accupdatefamily", {"scope": circle_id, "name": name}) + client.logout() + except FamilyWallError as exc: + return f"Error: Family Wall API error: {exc}" + except Exception as exc: + return f"Error: Connection error: {exc}" + + # Response: a00.r.r = full circle object + try: + circle_obj: dict[str, Any] = resp["a00"]["r"]["r"] + if not isinstance(circle_obj, dict) or "metaId" not in circle_obj: + raise TypeError("unexpected shape") + except (KeyError, TypeError): + return json.dumps( + {"warning": "Unexpected accupdatefamily response structure", "raw": resp}, + ensure_ascii=False, + indent=2, + ) + + return json.dumps( + { + "updated": True, + "id": circle_obj.get("metaId"), + "name": circle_obj.get("name"), + }, + ensure_ascii=False, + indent=2, + ) + + # --------------------------------------------------------------------------- # Tool: add_member_to_circle # ---------------------------------------------------------------------------