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 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 12:24:48 +02:00
parent 0ed9d62e4a
commit a26a637c83
6 changed files with 54 additions and 10 deletions
+3 -2
View File
@@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden.
## Aktueller Stand ## Aktueller Stand
### Implementierte Tools (v0.11.2) ### Implementierte Tools (v0.11.3)
| Kategorie | Tools | | 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.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.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.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) - v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
+1 -1
View File
@@ -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. 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 ### Read
+17 -3
View File
@@ -764,10 +764,24 @@ POST https://api.familywall.com/api/mpcreate
| `type` | ja | Mahlzeiten-Typ: `BREAKFAST`, `LUNCH`, `SNACK`, `DINNER` | | `type` | ja | Mahlzeiten-Typ: `BREAKFAST`, `LUNCH`, `SNACK`, `DINNER` |
| `clientOpId` | nein | Optionale Client-seitige Idempotenz-ID (wird weggelassen) | | `clientOpId` | nein | Optionale Client-seitige Idempotenz-ID (wird weggelassen) |
**Response-Struktur:** TBD — Tool liefert Raw JSON zur Verifizierung (→ v0.11.3). **Response-Struktur:**
Erwartet: dish-Objekt analog zu `mpcreateByRecipeId`, aber ohne `recipeId`. ```
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) ### Weitere Meal Planner Endpoints (nicht implementiert)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "mcp-familywall" 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" description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+1 -1
View File
@@ -1 +1 @@
__version__ = "0.11.2" __version__ = "0.11.3"
+31 -2
View File
@@ -2430,8 +2430,37 @@ def add_meal_to_meal_plan(
except RuntimeError as exc: except RuntimeError as exc:
return f"Error: {exc}" return f"Error: {exc}"
# Return raw response until the structure is verified in production. # NOTE: mpcreate returns a00.r.r as an *array*, unlike mpcreateByRecipeId
return json.dumps(data, ensure_ascii=False, indent=2) # 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)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------