feat: add delete_circle tool (v0.7.2)

Implements `delete_circle(circle_id)` using the verified `adminwipefamily`
endpoint. Protects the primary circle via `isFirstFamily` check. Probe
circles family/23447370 and family/23447378 cleaned up during testing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-16 18:28:22 +02:00
parent abb557e96b
commit dc21416a61
6 changed files with 125 additions and 9 deletions
+6 -4
View File
@@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden.
## Aktueller Stand
### Implementierte Tools (v0.7.1)
### Implementierte Tools (v0.7.2)
| 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`, `add_member_to_circle` |
| Kreise | `create_circle`, `delete_circle`, `add_member_to_circle` |
## Roadmap
@@ -46,8 +46,9 @@ und wird in Claude Desktop eingebunden.
- v0.6.0: Rezept-Box (get_recipes, get_recipe, create_recipe, delete_recipe) ✓
- v0.6.1: update_recipe + Bugfix Zeilenumbrüche in create_recipe ✓
- v0.7.0: create_circle + add_member_to_circle ✓
- v0.7.1: get_lists scope fix + create_list circle_id + delete_list scope ✓ ← aktuell
- v0.7.2: mpadditemtolist (Zutaten → Einkaufsliste)
- v0.7.1: get_lists scope fix + create_list circle_id + delete_list scope ✓
- v0.7.2: delete_circle ✓ ← aktuell
- v0.7.3: mpadditemtolist (Zutaten → Einkaufsliste)
- v0.5.3: update_list (Umbenennen, emoji/color ändern), Sharing-Verwaltung
- v0.8.x: Erinnerungen + Wiederholungen (Premium-Account erforderlich)
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
@@ -147,6 +148,7 @@ Fehler bei falschen Parametern kommen nicht immer auf Top-Level:
| `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`) |
| `adminwipefamily` | `scope` | Kreis-metaId; löscht Kreis + alle Inhalte; `a00.r.r="true"` bei Erfolg |
### Self-Like-Restriction
Eigene Posts können nicht geliked werden. API antwortet 200, macht aber nichts.
+3 -2
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.
## Features (v0.7.1)
## Features (v0.7.2)
### Read
@@ -29,7 +29,8 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your
- `create_recipe` -- create a new recipe (name, description, ingredients, instructions, prep/cook time, serves, url); use `\n` to separate ingredient lines
- `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); note: circles cannot be deleted via API
- `create_circle` -- create a new Family Wall circle (group)
- `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)
## Requirements
+25 -1
View File
@@ -583,6 +583,31 @@ POST https://api.familywall.com/api/accupdatefamily
**Verifiziert am:** 2026-04-16 via FW_DEBUG=1
### `adminwipefamily` Kreis löschen
POST https://api.familywall.com/api/adminwipefamily
Löscht einen Kreis und alle zugehörigen Inhalte (Listen, Tasks, Rezepte, Wall-Posts).
**Body-Parameter:**
| Parameter | Pflicht | Wert |
|---|---|---|
| `scope` | ja | Kreis-metaId (z.B. `family/23447378`) |
**Response (Erfolg):**
```json
{ "a00": { "r": { "r": "true" } } }
```
**Hinweise:**
- Löscht unwiderruflich alle Inhalte des Kreises.
- Der primäre Kreis (`isFirstFamily="true"` in `famlistfamily`) kann NICHT gelöscht werden.
- MCP-Server prüft `isFirstFamily` vor dem Löschen via `famlistfamily` im gleichen Session-Call.
**Verifiziert am:** 2026-04-16 via FW_DEBUG=1 (family/23447371 erfolgreich gelöscht)
## Offene Punkte
- Unlike-Endpoint (Service Worker blockiert Analyse)
@@ -591,4 +616,3 @@ POST https://api.familywall.com/api/accupdatefamily
- Sortierung von Kategorien via API
- mpadditemtolist (Zutaten aus Rezept → Einkaufsliste)
- Einladung bestehender FamilyWall-Nutzer (accinvite nur für neue Accounts)
- Kreis-Delete-Endpoint (API: "delete not supported" für family-Objekte)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project]
name = "mcp-familywall"
version = "0.7.1"
version = "0.7.2"
description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
readme = "README.md"
requires-python = ">=3.12"
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.7.1"
__version__ = "0.7.2"
+89
View File
@@ -1289,6 +1289,95 @@ def add_member_to_circle(
)
# ---------------------------------------------------------------------------
# Tool: delete_circle
# ---------------------------------------------------------------------------
@mcp.tool()
def delete_circle(circle_id: str) -> str:
"""Permanently delete a Family Wall circle (group) and all its content.
IMPORTANT: Ask the user for confirmation before calling this tool.
This action cannot be undone. All lists, tasks, recipes, and wall posts
inside the circle are deleted along with it. The primary (first) circle
is protected and cannot be deleted.
Args:
circle_id: Circle metaId from ``get_circles``
(e.g. ``"family/23447378"``). Must not be the primary circle.
Returns:
JSON success indicator or an error message.
"""
try:
email, password = get_credentials()
except RuntimeError as exc:
return f"Error: {exc}"
circle_name: str = circle_id
try:
with FamilyWallClient() as client:
client.login(email, password)
# Fetch the circle list to verify the target exists and is not primary.
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,
)
if target.get("isFirstFamily") == "true":
client.logout()
return json.dumps(
{
"error": "Cannot delete the primary circle.",
"id": circle_id,
"name": target.get("name"),
"hint": (
"The primary (first) circle cannot be deleted via the API. "
"Use the Family Wall app settings to manage it."
),
},
ensure_ascii=False,
indent=2,
)
circle_name = target.get("name", circle_id)
# Verified — delete in the same session.
# adminwipefamily uses scope=<circle_metaId> and returns a00.r.r="true".
client.call("adminwipefamily", {"scope": circle_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": circle_id, "name": circle_name},
ensure_ascii=False,
indent=2,
)
# ---------------------------------------------------------------------------
# Tool: like_post
# ---------------------------------------------------------------------------