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
+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
# ---------------------------------------------------------------------------