feat(meal-planner): structured output for get_meal_plan (v0.10.1)
- Map mplistinterval response to clean JSON list (id, date, type, name, recipe_id, can_update, can_delete) — no more raw dump - SPEC.md: document verified mplistinterval response structure - Fix two pre-existing ruff SIM warnings (SIM102, SIM105) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -24,7 +24,7 @@ und wird in Claude Desktop eingebunden.
|
|||||||
|
|
||||||
## Aktueller Stand
|
## Aktueller Stand
|
||||||
|
|
||||||
### Implementierte Tools (v0.10.0)
|
### Implementierte Tools (v0.10.1)
|
||||||
|
|
||||||
| Kategorie | Tools |
|
| Kategorie | Tools |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -58,7 +58,8 @@ und wird in Claude Desktop eingebunden.
|
|||||||
- v0.8.x: mpadditemtolist (gestrichen – Family Wall kann das nativ)
|
- 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.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.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)
|
- v2.0: Schreibzugriff auf Wall-Posts (Erstellen, Kommentieren)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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.10.0)
|
## Features (v0.10.1)
|
||||||
|
|
||||||
### Read
|
### 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_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` -- 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_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)
|
### Write (with confirmation prompt)
|
||||||
|
|
||||||
|
|||||||
@@ -677,10 +677,28 @@ POST https://api.familywall.com/api/mplistinterval
|
|||||||
|
|
||||||
| Parameter | Pflicht | Wert |
|
| Parameter | Pflicht | Wert |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `from` | ja | Start-Datum (ISO 8601, Format TBD: `"2026-04-13"` oder `"2026-04-13T00:00:00"`) |
|
| `from` | ja | Start-Datum ISO 8601 (z.B. `"2026-04-13"`) |
|
||||||
| `to` | ja | End-Datum (ISO 8601, Format TBD) |
|
| `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/<family_num>_<dish_num>"
|
||||||
|
.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:**
|
**Mahlzeiten-Typen:**
|
||||||
- `BREAKFAST` – Frühstück
|
- `BREAKFAST` – Frühstück
|
||||||
@@ -688,9 +706,10 @@ POST https://api.familywall.com/api/mplistinterval
|
|||||||
- `SNACK` – Snack
|
- `SNACK` – Snack
|
||||||
- `DINNER` – Abendessen
|
- `DINNER` – Abendessen
|
||||||
|
|
||||||
**Mahlzeiten-Objekt:** MetaId-Format `dish/<id>` (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)
|
### Weitere Meal Planner Endpoints (nicht implementiert)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "mcp-familywall"
|
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"
|
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 @@
|
|||||||
__version__ = "0.10.0"
|
__version__ = "0.10.1"
|
||||||
|
|||||||
@@ -159,9 +159,7 @@ def build_create_params(
|
|||||||
params["recipe.serves"] = str(serves)
|
params["recipe.serves"] = str(serves)
|
||||||
if url is not None:
|
if url is not None:
|
||||||
params["recipe.url"] = url
|
params["recipe.url"] = url
|
||||||
if category_ids is not None:
|
if category_ids is not None and len(category_ids) > 0:
|
||||||
# For create: empty list just means no categories (don't send parameter).
|
|
||||||
if len(category_ids) > 0:
|
|
||||||
params["recipe.recipeCategoryIdList"] = category_ids
|
params["recipe.recipeCategoryIdList"] = category_ids
|
||||||
return params
|
return params
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import contextlib
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
@@ -1826,10 +1827,8 @@ def get_recipe_categories() -> str:
|
|||||||
"""
|
"""
|
||||||
# Get the family ID to construct standard category IDs
|
# Get the family ID to construct standard category IDs
|
||||||
family_id: str | None = None
|
family_id: str | None = None
|
||||||
try:
|
with contextlib.suppress(RuntimeError):
|
||||||
family_id = _get_family_id()
|
family_id = _get_family_id()
|
||||||
except RuntimeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Collect all category IDs: standard categories + categories found in recipes
|
# Collect all category IDs: standard categories + categories found in recipes
|
||||||
seen_ids: set[str] = set()
|
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"``).
|
date_to: End date in ISO format (e.g. ``"2026-04-19"``).
|
||||||
|
|
||||||
Returns:
|
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:
|
try:
|
||||||
data = _authenticated_call("mplistinterval", {"from": date_from, "to": date_to})
|
data = _authenticated_call("mplistinterval", {"from": date_from, "to": date_to})
|
||||||
except RuntimeError as exc:
|
except RuntimeError as exc:
|
||||||
return f"Error: {exc}"
|
return f"Error: {exc}"
|
||||||
|
|
||||||
# Return raw JSON for verification — response structure not yet known
|
try:
|
||||||
return json.dumps(data, ensure_ascii=False, indent=2)
|
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)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user