diff --git a/CLAUDE.md b/CLAUDE.md index 5cc8c2e..3b9fc4f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden. ## Aktueller Stand -### Implementierte Tools (v0.10.1) +### Implementierte Tools (v0.10.2) | Kategorie | Tools | |---|---| @@ -59,7 +59,8 @@ und wird in Claude Desktop eingebunden. - v0.9.0: get_tasks liefert recurrency, recurrency_interval, rrule, reminder (read-only) ✓ - v0.9.1: Bugfix reminder-Mapping (reminderUnit/reminderValue statt unit/value; value=0 ist gültig) ✓ - v0.10.0: get_meal_plan (read-only, raw JSON; Premium-Feature Essensplaner) ✓ -- v0.10.1: get_meal_plan strukturierter Output + SPEC.md mplistinterval Response verifiziert ✓ ← aktuell +- v0.10.1: get_meal_plan strukturierter Output + SPEC.md mplistinterval Response verifiziert ✓ +- v0.10.2: get_meal_plan mealList[] einbinden (Freitext-Notizen + Portionen), merged + sortiert ✓ ← aktuell - v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren) diff --git a/README.md b/README.md index 9944afb..7f8c942 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.10.1) +## Features (v0.10.2) ### Read @@ -15,7 +15,7 @@ MCP server for [Family Wall](https://www.familywall.com) -- read and manage your - `get_recipes` -- list all family recipes (compact summary: id, name, prep/cook time, serves) - `get_recipe` -- get a single recipe in full detail (ingredients, instructions, ingredients_parsed, category_ids, etc.) - `get_recipe_categories` -- list all available recipe categories (always returns all 5 standard categories: Bei Kindern beliebt, Wirklich einfach, Nachspeisen, Schmeckt toll, Gemüse; plus any additional categories found in existing recipes) -- `get_meal_plan` -- get meal plan entries for a date range (Premium feature; returns structured list with id, date, type, name, recipe_id, can_update, can_delete) +- `get_meal_plan` -- get meal plan entries for a date range (Premium feature; merges dish and meal entries, sorted by date + type; fields: id, date, type, name, recipe_id, note, serves, can_update, can_delete) ### Write (with confirmation prompt) diff --git a/SPEC.md b/SPEC.md index 1fbb453..c571e75 100644 --- a/SPEC.md +++ b/SPEC.md @@ -697,7 +697,14 @@ a00.r.r .rights.canUpdate → "true" wenn bearbeitbar .rights.canDelete → "true" wenn löschbar .recipeList[] → vollständige Rezept-Objekte der verknüpften Rezepte - .mealList[] → immer leer (Bedeutung unbekannt) + .mealList[] → Freitext-Notizen (meal-Objekte) + .metaId → "meal/_" + .date → Datum der Mahlzeit (z.B. "2026-04-17") + .type → Mahlzeiten-Typ (BREAKFAST/LUNCH/SNACK/DINNER) + .note → Freitext-Notiz (z.B. "Test") + .serves → Portionen als String (z.B. "1") + .rights.canUpdate → "true" wenn bearbeitbar + .rights.canDelete → "true" wenn löschbar ``` **Mahlzeiten-Typen:** @@ -706,8 +713,9 @@ a00.r.r - `SNACK` – Snack - `DINNER` – Abendessen -**Hinweis:** `recipeList` und `mealList` werden vom MCP-Tool nicht zurückgegeben. +**Hinweis:** `recipeList` wird vom MCP-Tool nicht zurückgegeben. Rezept-Details bei Bedarf separat via `get_recipe` abrufen. +`list[]` (dish) und `mealList[]` (meal) werden gemergt und nach Datum + Typ sortiert zurückgegeben. **Verifiziert am:** 2026-04-17 via FW_DEBUG=1 diff --git a/pyproject.toml b/pyproject.toml index 65bcd81..3255243 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.10.1" +version = "0.10.2" 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 1f4c4d4..17c1a62 100644 --- a/src/mcp_familywall/__init__.py +++ b/src/mcp_familywall/__init__.py @@ -1 +1 @@ -__version__ = "0.10.1" +__version__ = "0.10.2" diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index e08e9b7..b368de1 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -2218,16 +2218,29 @@ def update_recipe( def get_meal_plan(date_from: str, date_to: str) -> str: """Return meal plan entries for a date range. + The response merges two API lists: + + * ``dish`` entries — planned meals, optionally linked to a recipe. + * ``meal`` entries — free-text notes with an optional serving count. + Args: date_from: Start date in ISO format (e.g. ``"2026-04-13"``). date_to: End date in ISO format (e.g. ``"2026-04-19"``). Returns: - JSON list of meal plan entries with keys: id, date, type, name, - recipe_id, can_update, can_delete. ``type`` is one of - ``BREAKFAST``, ``LUNCH``, ``SNACK``, ``DINNER``. - ``recipe_id`` is the linked recipe metaId or ``null`` when the - entry is a free-text dish (not linked to a recipe). + JSON list of meal plan entries sorted by date then meal type + (BREAKFAST → LUNCH → SNACK → DINNER), with keys: + + - ``id`` — metaId (``dish/…`` or ``meal/…``) + - ``date`` — ISO date string + - ``type`` — ``BREAKFAST``, ``LUNCH``, ``SNACK``, or ``DINNER`` + - ``name`` — dish name, or ``null`` for meal entries + - ``recipe_id`` — linked recipe metaId, or ``null`` + - ``note`` — free-text note (meal entries only), or ``null`` + - ``serves`` — number of servings as int (meal entries only), or ``null`` + - ``can_update`` — whether the entry can be updated + - ``can_delete`` — whether the entry can be deleted + Returns an error message string on failure. """ try: @@ -2236,9 +2249,9 @@ def get_meal_plan(date_from: str, date_to: str) -> str: return f"Error: {exc}" try: - raw_list = data["a00"]["r"]["r"]["list"] - if not isinstance(raw_list, list): - raise TypeError("a00.r.r.list is not a list") + payload = data["a00"]["r"]["r"] + if not isinstance(payload, dict): + raise TypeError("a00.r.r is not a dict") except (KeyError, TypeError): return json.dumps( {"warning": "Unexpected mplistinterval response structure", "raw": data}, @@ -2246,8 +2259,14 @@ def get_meal_plan(date_from: str, date_to: str) -> str: indent=2, ) - result = [] - for dish in raw_list: + raw_dishes: list[dict[str, Any]] = payload.get("list") or [] + raw_meals: list[dict[str, Any]] = payload.get("mealList") or [] + + _type_order = {"BREAKFAST": 0, "LUNCH": 1, "SNACK": 2, "DINNER": 3} + + result: list[dict[str, Any]] = [] + + for dish in raw_dishes: rights = dish.get("rights") or {} result.append( { @@ -2256,11 +2275,32 @@ def get_meal_plan(date_from: str, date_to: str) -> str: "type": dish.get("type"), "name": dish.get("name"), "recipe_id": dish.get("recipeId") or None, + "note": None, + "serves": None, "can_update": rights.get("canUpdate") == "true", "can_delete": rights.get("canDelete") == "true", } ) + for meal in raw_meals: + rights = meal.get("rights") or {} + raw_serves = meal.get("serves") + result.append( + { + "id": meal.get("metaId"), + "date": meal.get("date"), + "type": meal.get("type"), + "name": None, + "recipe_id": None, + "note": meal.get("note") or None, + "serves": int(raw_serves) if raw_serves is not None else None, + "can_update": rights.get("canUpdate") == "true", + "can_delete": rights.get("canDelete") == "true", + } + ) + + result.sort(key=lambda e: (e.get("date") or "", _type_order.get(e.get("type") or "", 99))) + return json.dumps(result, ensure_ascii=False, indent=2)