From a26a637c83ccf96f15b40b807a56cb325a02c21b Mon Sep 17 00:00:00 2001 From: Marcus van Elst Date: Fri, 17 Apr 2026 12:24:48 +0200 Subject: [PATCH] feat(meal-planner): structured output for add_meal_to_meal_plan (v0.11.3) Map verified mpcreate response to same field layout as get_meal_plan. Key difference: a00.r.r is an array (take [0]) unlike mpcreateByRecipeId which returns a plain object. is_from_recipe_box is always false. Co-Authored-By: Claude Sonnet 4.6 --- CLAUDE.md | 5 +++-- README.md | 2 +- SPEC.md | 20 +++++++++++++++++--- pyproject.toml | 2 +- src/mcp_familywall/__init__.py | 2 +- src/mcp_familywall/server.py | 33 +++++++++++++++++++++++++++++++-- 6 files changed, 54 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5a234af..86b98f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden. ## Aktueller Stand -### Implementierte Tools (v0.11.2) +### Implementierte Tools (v0.11.3) | Kategorie | Tools | |---|---| @@ -65,7 +65,8 @@ und wird in Claude Desktop eingebunden. - v0.10.3: get_meal_plan is_from_recipe_box Feld (recipeList[].isRecipe Lookup) ✓ - v0.11.0: add_recipe_to_meal_plan (mpcreateByRecipeId; raw response bis Struktur verifiziert) ✓ - v0.11.1: add_recipe_to_meal_plan strukturierter Output (Response verifiziert) ✓ -- v0.11.2: add_meal_to_meal_plan (mpcreate; Freitext; raw response bis Struktur verifiziert) ✓ ← aktuell +- v0.11.2: add_meal_to_meal_plan (mpcreate; Freitext; raw response bis Struktur verifiziert) ✓ +- v0.11.3: add_meal_to_meal_plan strukturierter Output (a00.r.r ist Array, nicht Objekt) ✓ ← aktuell - v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren) diff --git a/README.md b/README.md index 9f96bdf..3624117 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.11.2) +## Features (v0.11.3) ### Read diff --git a/SPEC.md b/SPEC.md index 7dfbed8..59cc599 100644 --- a/SPEC.md +++ b/SPEC.md @@ -764,10 +764,24 @@ POST https://api.familywall.com/api/mpcreate | `type` | ja | Mahlzeiten-Typ: `BREAKFAST`, `LUNCH`, `SNACK`, `DINNER` | | `clientOpId` | nein | Optionale Client-seitige Idempotenz-ID (wird weggelassen) | -**Response-Struktur:** TBD — Tool liefert Raw JSON zur Verifizierung (→ v0.11.3). -Erwartet: dish-Objekt analog zu `mpcreateByRecipeId`, aber ohne `recipeId`. +**Response-Struktur:** +``` +a00.r.r → Array (⚠️ nicht Objekt wie bei mpcreateByRecipeId!) + [0] → neues dish-Objekt + .metaId → neue Dish-ID (z.B. "dish/16282169_20010208") + .date → Datum (z.B. "2026-04-20") + .type → Mahlzeiten-Typ (BREAKFAST/LUNCH/SNACK/DINNER) + .name → Freitext-Name + .recipeId → vom Server generierte Stub-Rezept-ID (isRecipe="false") + .rights.canUpdate → "true" + .rights.canDelete → "true" +a00.cn → "mpcreate" (Endpoint-Echo) +``` -**Verifiziert am:** 2026-04-17 (Parameter aus JS-Bundle; Response TBD) +**Hinweis:** Der Server legt intern ein Stub-Rezept (`isRecipe="false"`) an und +verknüpft es mit dem Dish-Objekt. `is_from_recipe_box` ist daher `false`. + +**Verifiziert am:** 2026-04-17 via FW_DEBUG=1 ### Weitere Meal Planner Endpoints (nicht implementiert) diff --git a/pyproject.toml b/pyproject.toml index f71cca4..0fb0913 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "mcp-familywall" -version = "0.11.2" +version = "0.11.3" 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 e2bd072..1bebb74 100644 --- a/src/mcp_familywall/__init__.py +++ b/src/mcp_familywall/__init__.py @@ -1 +1 @@ -__version__ = "0.11.2" +__version__ = "0.11.3" diff --git a/src/mcp_familywall/server.py b/src/mcp_familywall/server.py index 3b6c947..ce7e309 100644 --- a/src/mcp_familywall/server.py +++ b/src/mcp_familywall/server.py @@ -2430,8 +2430,37 @@ def add_meal_to_meal_plan( except RuntimeError as exc: return f"Error: {exc}" - # Return raw response until the structure is verified in production. - return json.dumps(data, ensure_ascii=False, indent=2) + # NOTE: mpcreate returns a00.r.r as an *array*, unlike mpcreateByRecipeId + # which returns a plain object. Take the first (and only) element. + try: + items = data["a00"]["r"]["r"] + if not isinstance(items, list) or not items: + raise TypeError("a00.r.r is not a non-empty list") + dish = items[0] + if not isinstance(dish, dict) or "metaId" not in dish: + raise TypeError("unexpected dish shape") + except (KeyError, TypeError): + return json.dumps( + {"warning": "Unexpected mpcreate response structure", "raw": data}, + ensure_ascii=False, + indent=2, + ) + + rights = dish.get("rights") or {} + result: dict[str, Any] = { + "id": dish.get("metaId"), + "date": dish.get("date"), + "type": dish.get("type"), + "name": dish.get("name"), + "recipe_id": dish.get("recipeId") or None, + # mpcreate always creates free-text entries (not from the recipe box). + "is_from_recipe_box": False, + "note": None, + "serves": None, + "can_update": rights.get("canUpdate") == "true", + "can_delete": rights.get("canDelete") == "true", + } + return json.dumps(result, ensure_ascii=False, indent=2) # ---------------------------------------------------------------------------