diff --git a/CLAUDE.md b/CLAUDE.md index 39dd777..5cc8c2e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden. ## Aktueller Stand -### Implementierte Tools (v0.10.0) +### Implementierte Tools (v0.10.1) | Kategorie | Tools | |---|---| @@ -58,7 +58,8 @@ und wird in Claude Desktop eingebunden. - v0.8.x: mpadditemtolist (gestrichen – Family Wall kann das nativ) - 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) ✓ ← aktuell +- 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 - v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren) diff --git a/README.md b/README.md index 0b21420..9944afb 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.0) +## Features (v0.10.1) ### 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 raw JSON for verification) +- `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) ### Write (with confirmation prompt) diff --git a/SPEC.md b/SPEC.md index 9aa6dac..1fbb453 100644 --- a/SPEC.md +++ b/SPEC.md @@ -677,10 +677,28 @@ POST https://api.familywall.com/api/mplistinterval | Parameter | Pflicht | Wert | |---|---|---| -| `from` | ja | Start-Datum (ISO 8601, Format TBD: `"2026-04-13"` oder `"2026-04-13T00:00:00"`) | -| `to` | ja | End-Datum (ISO 8601, Format TBD) | +| `from` | ja | Start-Datum ISO 8601 (z.B. `"2026-04-13"`) | +| `to` | ja | End-Datum ISO 8601 (z.B. `"2026-04-19"`) | -**Response-Struktur:** TBD — Tool liefert Raw JSON zur Verifizierung. +**Response-Struktur:** +``` +a00.r.r + .from → angefragtes Start-Datum (z.B. "2026-04-13") + .to → angefragtes End-Datum (z.B. "2026-04-19") + .list[] → geplante Mahlzeiten (dish-Objekte) + .metaId → "dish/_" + .date → Datum der Mahlzeit (z.B. "2026-04-17") + .type → Mahlzeiten-Typ (s.u.) + .name → Anzeigename (z.B. "Biga Pizzateig") + .recipeId → verknüpfte Rezept-metaId oder fehlt wenn freier Text + .familyId → Kreis-metaId + .accountId → Ersteller-accountId + .sortingIndex → Sortierung (numerischer Timestamp als String) + .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) +``` **Mahlzeiten-Typen:** - `BREAKFAST` – Frühstück @@ -688,9 +706,10 @@ POST https://api.familywall.com/api/mplistinterval - `SNACK` – Snack - `DINNER` – Abendessen -**Mahlzeiten-Objekt:** MetaId-Format `dish/` (Präfix "dish/"). +**Hinweis:** `recipeList` und `mealList` werden vom MCP-Tool nicht zurückgegeben. +Rezept-Details bei Bedarf separat via `get_recipe` abrufen. -**Verifiziert am:** 2026-04-17 (Endpoint-Name aus JS-Bundle, Response TBD) +**Verifiziert am:** 2026-04-17 via FW_DEBUG=1 ### Weitere Meal Planner Endpoints (nicht implementiert) diff --git a/pyproject.toml b/pyproject.toml index 6e5fe7f..65bcd81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.10.0" +version = "0.10.1" 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 61fb31c..1f4c4d4 100644 --- a/src/mcp_familywall/__init__.py +++ b/src/mcp_familywall/__init__.py @@ -1 +1 @@ -__version__ = "0.10.0" +__version__ = "0.10.1" diff --git a/src/mcp_familywall/modules/recipes.py b/src/mcp_familywall/modules/recipes.py index 8adda35..9950661 100644 --- a/src/mcp_familywall/modules/recipes.py +++ b/src/mcp_familywall/modules/recipes.py @@ -159,10 +159,8 @@ def build_create_params( params["recipe.serves"] = str(serves) if url is not None: params["recipe.url"] = url - if category_ids is not None: - # For create: empty list just means no categories (don't send parameter). - if len(category_ids) > 0: - params["recipe.recipeCategoryIdList"] = category_ids + if category_ids is not None and len(category_ids) > 0: + params["recipe.recipeCategoryIdList"] = category_ids return params diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index 43d2727..e08e9b7 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -2,6 +2,7 @@ from __future__ import annotations +import contextlib import json import logging import os @@ -1826,10 +1827,8 @@ def get_recipe_categories() -> str: """ # Get the family ID to construct standard category IDs family_id: str | None = None - try: + with contextlib.suppress(RuntimeError): family_id = _get_family_id() - except RuntimeError: - pass # Collect all category IDs: standard categories + categories found in recipes seen_ids: set[str] = set() @@ -2224,15 +2223,45 @@ def get_meal_plan(date_from: str, date_to: str) -> str: date_to: End date in ISO format (e.g. ``"2026-04-19"``). Returns: - Raw JSON response from the API for initial verification. + 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). + Returns an error message string on failure. """ try: data = _authenticated_call("mplistinterval", {"from": date_from, "to": date_to}) except RuntimeError as exc: return f"Error: {exc}" - # Return raw JSON for verification — response structure not yet known - return json.dumps(data, ensure_ascii=False, indent=2) + try: + raw_list = data["a00"]["r"]["r"]["list"] + if not isinstance(raw_list, list): + raise TypeError("a00.r.r.list is not a list") + except (KeyError, TypeError): + return json.dumps( + {"warning": "Unexpected mplistinterval response structure", "raw": data}, + ensure_ascii=False, + indent=2, + ) + + result = [] + for dish in raw_list: + rights = dish.get("rights") or {} + result.append( + { + "id": dish.get("metaId"), + "date": dish.get("date"), + "type": dish.get("type"), + "name": dish.get("name"), + "recipe_id": dish.get("recipeId") or None, + "can_update": rights.get("canUpdate") == "true", + "can_delete": rights.get("canDelete") == "true", + } + ) + + return json.dumps(result, ensure_ascii=False, indent=2) # ---------------------------------------------------------------------------