feat(circles): add update_circle tool (v0.7.4)

Implements accupdatefamily endpoint (verified via FW_DEBUG=1):
- Parameter 'scope' targets any circle (primary or secondary)
- Without scope: renames the primary circle (API default)
- Server always capitalises the first letter of the new name
- Verifies circle existence via famlistfamily in same session
- Response a00.r.r = full circle object with updated name

Also corrects SPEC.md: accupdatefamily with scope= works for any
circle, not just the primary (previous note was incomplete).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 21:33:07 +02:00
parent d144a77662
commit 02f9d62720
6 changed files with 120 additions and 10 deletions
+6 -5
View File
@@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden.
## Aktueller Stand ## Aktueller Stand
### Implementierte Tools (v0.7.3) ### Implementierte Tools (v0.7.4)
| Kategorie | Tools | | Kategorie | Tools |
|---|---| |---|---|
@@ -34,7 +34,7 @@ und wird in Claude Desktop eingebunden.
| Kategorien | `create_category`, `delete_category` | | Kategorien | `create_category`, `delete_category` |
| Aktivitäten | `like_post` | | Aktivitäten | `like_post` |
| Rezepte | `get_recipes`, `get_recipe`, `create_recipe`, `update_recipe`, `delete_recipe` | | 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 ## Roadmap
@@ -48,8 +48,9 @@ und wird in Claude Desktop eingebunden.
- v0.7.0: create_circle + add_member_to_circle ✓ - 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.1: get_lists scope fix + create_list circle_id + delete_list scope ✓
- v0.7.2: delete_circle ✓ - v0.7.2: delete_circle ✓
- v0.7.3: update_list (Umbenennen, emoji/color ändern) ✓ ← aktuell - v0.7.3: update_list (Umbenennen, emoji/color ändern) ✓
- v0.7.4: mpadditemtolist (Zutaten → Einkaufsliste) - v0.7.4: update_circle (Kreis umbenennen) ✓ ← aktuell
- v0.7.5: mpadditemtolist (Zutaten → Einkaufsliste)
- v0.8.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich) - v0.8.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren) - 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[]` | | `metasync` (Rezepte lesen) | `id="recipe"` | liefert `a00.r.r.updatedCreated[]` |
| `acccreatefamily` | `name` | liefert numerische Kreis-ID als String in `a00.r.r` | | `acccreatefamily` | `name` | liefert numerische Kreis-ID als String in `a00.r.r` |
| `accinvite` | `familyId`, `identifier`, `role="Unknown"`, `firstname` | nur für neue FW-Accounts | | `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 | | `adminwipefamily` | `scope` | Kreis-metaId; löscht Kreis + alle Inhalte; `a00.r.r="true"` bei Erfolg |
### Self-Like-Restriction ### Self-Like-Restriction
+2 -1
View File
@@ -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. 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 ### 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) - `update_recipe` -- update any field of an existing recipe (partial update — omitted fields unchanged)
- `delete_recipe` -- permanently delete a recipe (only own recipes) - `delete_recipe` -- permanently delete a recipe (only own recipes)
- `create_circle` -- create a new Family Wall circle (group) - `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) - `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) - `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)
+20 -2
View File
@@ -611,10 +611,28 @@ POST https://api.familywall.com/api/accupdatefamily
| Parameter | Pflicht | Wert | | Parameter | Pflicht | Wert |
|---|---|---| |---|---|---|
| `name` | ja | Neuer Kreis-Name | | `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 ### `adminwipefamily` Kreis löschen
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" 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" 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"
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.7.3" __version__ = "0.7.4"
+90
View File
@@ -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=<circle_metaId> 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 # Tool: add_member_to_circle
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------