- Move integration tests to tests/; fix .gitignore to scope root-only
- Remove tracked debug artefacts (probe2_stderr/stdout.txt)
- __init__.py: version via importlib.metadata; export create_server in __all__
- server.py: unified JSON error format {"error":"..."} for all tools
- server.py: date validation (YYYY-MM-DD) for all meal-plan tools
- server.py: clear_list reports partial failures (failed_count, failed_ids)
- server.py: -> str annotations on get_circles, get_tasks, get_activities
- server.py: document TODO:94 as known limitation (no name in sortingIndexByTaskList)
- server.py: date validation also added to get_meal_plan
- Add LICENSE (MIT, Marcus van Elst)
- Add CHANGELOG.md (Keep a Changelog, v0.1.0–v1.0.0)
- README.md: restructured by use case; 🔒 marks write tools
- CLAUDE.md: update to v1.0.0 state; condense roadmap history
- SPEC.md: add version stamp
- pyproject.toml: version 1.0.0 (single source of truth)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.6 KiB
mcp-familywall
Kontext
Dieses Projekt entwickelt mcp-familywall – einen MCP-Server für den
Zugriff auf Family Wall (familywall.com). Der MCP-Server läuft lokal
und wird in Claude Desktop eingebunden.
Infrastruktur
| Family Wall API | https://api.familywall.com/api |
| Gitea | https://gitea.gecheckt.de/marcus/mcp-familywall |
| Lokaler Code | D:\Dev\Projects\mcp-familywall |
| Sprache | Python 3.12+, uv, MCP SDK, httpx, keyring, click, rich |
Deploy-Workflow (nach jeder Code-Änderung)
- Claude Code committet und pusht (bei Berechtigungsfehler: bis zu 2 Retries, je 1s warten)
- Marcus installiert lokal und startet Claude Desktop neu
Aktueller Stand
Version: v1.0.0 ← aktuell
Implementierte Tools
| Kategorie | Tools |
|---|---|
| Kreise & Mitglieder | get_circles, get_members, create_circle, update_circle, delete_circle, add_member_to_circle |
| Listen & Tasks | get_lists, get_tasks, get_categories, get_activities, create_list, update_list, delete_list, create_category, delete_category, create_task, update_task, toggle_task, delete_task, clear_list, like_post |
| Rezeptbox | get_recipe_categories, get_recipe_box, get_recipes, get_recipe, create_recipe, update_recipe, delete_recipe |
| Essensplaner | get_meal_plan, add_recipe_to_meal_plan, add_meal_to_meal_plan, add_meal_note, delete_meal_plan_entry |
Roadmap (Nächstes)
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
Historische Meilensteine (kompakt)
- v0.1–v0.4: Lesen (Kreise, Listen, Tasks, Kategorien), Schreiben (Tasks, Kategorien)
- v0.5: Listen-Management (create/delete), emoji + color
- v0.6–v0.8: Rezeptbox vollständig, Kreismanagement, Rezeptkategorien
- v0.9: Task-Wiederholungen + Erinnerungen (read-only)
- v0.10–v0.11: Essensplaner (read + write)
- v1.0: Cleanup, Unified errors, Datumsvalidierung, Partial-Failure-Reporting (Details: CHANGELOG.md)
Architektur-Entscheidungen
Session-Strategie
Kein Session-Caching. Jeder Tool-Call führt Login → API-Call → Logout durch.
Optimierung: Mehrere API-Calls in einer Session bündeln (login → call1 → call2 → logout)
um unnötige HTTP-Roundtrips zu vermeiden. Credentials liegen im OS Keyring
(nur email + password), kein session_id.
Kreise (Scopes)
Family Wall kennt mehrere Kreise (z.B. Familie, erweiterter Familienkreis).
Der API-Parameter scope=family/XXXX schaltet den Server-Kontext um.
taskgettasklistsohne scope → primärer Kreis; mit scope → angegebener Kreistaskcreatelistmit scope → neue Liste im angegebenen Kreistaskdeletelistmit scope → löscht Liste aus angegebenem Kreisget_lists(scope="family/XXXX")oderget_lists(scope="Kreis-Name")zur Filterung- Die Listen-metaId kodiert den Kreis:
taskList/<FAMNUM>_<LISTNUM>→family/<FAMNUM>
Listen-Namen
Systembezeichnungen (z.B. SYS-CAT-SHOPPINGLIST) werden in deutsche
Klarnamen übersetzt. Mapping-Tabelle in modules/lists.py.
Kategorien
- Kategorien sind family-wide, nicht list-spezifisch (
taskcategoryputgilt für alle Listen) - System-Kategorien:
rights.canDelete=null→ nicht löschbar - Custom-Kategorien:
rights.canDelete="true"→ löschbar - Locale-Filter: default
"de"– Custom-Kategorien haben kein locale-Feld und werden immer angezeigt unabhängig vom Locale-Parameter
Fehlerbehandlung
Die API gibt Fehler manchmal als a00.un.un zurück (nicht Top-Level).
fw_client.py prüft beides und wirft FamilyWallError. Nie silent-fail
als Erfolg werten – immer a00.un und Top-Level ex/un prüfen.
Service Worker
Die Family Wall Web-App nutzt einen Service Worker der bestimmte
HTTP-Requests abfängt und modifiziert. Browser-DevTools und JS-Interceptoren
können den echten Request-Body in diesen Fällen nicht sehen.
→ Immer FW_DEBUG=1 für Traffic-Analyse nutzen, nicht Browser-DevTools.
Claude Code – Implementierungsregeln
- Feature complete before next feature – jedes Feature vollständig implementieren, testen und verifizieren bevor das nächste beginnt
- Kein destruktives Probing – keine Probe-Calls auf System-Kategorien, echte Listen oder echte Tasks; immer Test-Objekte anlegen und danach sofort löschen
- Fehlerbehandlung: API-Fehler als verständliche Meldung zurückgeben, keine Stacktraces
- Keine Secrets in stderr-Ausgaben (Passwort bei Debug-Logging maskieren)
- Type Hints und Docstrings konsequent verwenden (Englisch)
- Formatter:
ruff format, Linter:ruff check, Tests:pytest - Alle Texte (Docstrings, Kommentare, README): Englisch
- Debug-Logging via
FW_DEBUG=1Umgebungsvariable - Nach jeder Aufgabe: git commit + push. Bei Berechtigungsfehler: 1s warten, bis zu 2 Retries
- .gitignore eigenständig pflegen (Credentials, pycache, .venv, .env, *.pyc etc.)
- README.md + CLAUDE.md im Projekt-Root bei jedem Aufruf aktualisieren
- Confirmation-Pattern in Docstrings:
IMPORTANT: Ask the user for confirmation before calling this tool.für destruktive oder schreibende Operationen
Bekannte API-Eigenheiten und Fallstricke
Sentinel-Wert $empty
Das FiZ-Framework nutzt $empty als Sentinel um optionale Felder zu löschen.
Normale Werte wie "", null, "null", "0" werden vom Server abgelehnt.
Aktuell genutzt für: dueDate=$empty (Fälligkeitsdatum entfernen)
Silent-Fail via a00.un.un
Fehler bei falschen Parametern kommen nicht immer auf Top-Level:
{"a00": {"un": {"un": {"message": "missing value in: taskId"}}}}
→ fw_client muss beide Ebenen prüfen
Bekannte Parameter-Namen (verifiziert)
| Endpoint | Parameter | Wert/Format |
|---|---|---|
taskcreate2 |
taskListId, text, description, taskCategoryId, dueDate, assignee |
– |
taskupdate2 |
metaId, text, description, taskCategoryId, dueDate, assignee, taskListId |
– |
taskupdate2 |
dueDate löschen |
$empty |
taskmark |
taskId, complete |
"true"/"false" |
metadelete |
id |
metaId des Tasks / Rezepts |
wallmood |
wall_message_id, moodType |
"STAR" für Like |
taskcategoryput |
name, emoji |
– |
taskcategorydelete |
id |
metaId der Kategorie |
taskcreatelist |
name, taskListType, sharedToAll, color, emoji, scope |
taskListType: SHOPPING_LIST, TODOS, OTHER; scope: Kreis-metaId für nicht-primäre Kreise |
taskgettasklists |
scope |
Kreis-metaId; ohne scope → primärer Kreis |
taskupdatelist |
metaId, name, color, emoji, scope |
metaId ⚠️ nicht id!; scope: Kreis-metaId für sekundäre Kreise; Partial Update |
taskdeletelist |
id, scope |
scope: Kreis-metaId für sekundäre Kreise |
mprecipeput |
recipe.name, recipe.isRecipe="true", recipe.description, recipe.ingredients, recipe.instructions, recipe.prepTime, recipe.cookTime, recipe.serves, recipe.url |
Alle mit recipe.-Prefix! |
mprecipeput (Update) |
zusätzlich recipe.metaId |
Vorhandene ID → Update statt Create |
mprecipeput (Kategorien) |
recipe.recipeCategoryIdList |
Mehrfach sendbar; leerer String "" entfernt alle Kategorien |
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, 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
Eigene Posts können nicht geliked werden. API antwortet 200, macht aber nichts.
Unlike nicht möglich
Service Worker verschlüsselt den Unlike-Request-Body.
Endpoint unbekannt. like_post(like=False) gibt Fehlermeldung zurück.
mpstar / Rezept-Favorit
Service Worker fängt mpstar ab. metamood funktioniert nur auf
fremde Inhalte (Self-Reaction-Restriction). Eigene Rezepte
können nicht als Favorit markiert werden. Siehe SPEC.md § Offene Punkte.
Test-Credentials (nur für Entwicklung)
marcus@gecheckt.de |
|
| Passwort | Lasdas1234 |
Hinweis: Das ist ein kostenloser Test-Account ohne Premium-Features. Der echte Account (Premium) hat andere Credentials die im Keyring gespeichert sind.
Hintergrund
Marcus ist Senior Software Engineer (Java, Jakarta EE). Präferenz: State-of-the-Art, Best Practices, saubere Architektur. Automatisierung spart Zeit für die Familie. 🌱