fix(recipes): two bugs in recipe categories (v0.8.1)
Bug 1: update_recipe(category_ids=[]) didn't remove categories - Fixed: send empty string "" to API to remove all categories - Non-empty lists continue to work as expected - Verified via FW_DEBUG=1 testing Bug 2: get_recipe_categories() only showed used categories - Fixed: always return 5 free-tier standard categories - Added _get_family_id() helper to extract family ID - Now returns all available category IDs even on new accounts - Standard categories: category/<familyId>_2 through _6 Version: 0.8.0 → 0.8.1 Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "mcp-familywall"
|
||||
version = "0.8.0"
|
||||
version = "0.8.1"
|
||||
description = "MCP server for Family Wall — read your family's lists and tasks via Claude"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = "0.8.0"
|
||||
__version__ = "0.8.1"
|
||||
|
||||
@@ -160,7 +160,9 @@ def build_create_params(
|
||||
if url is not None:
|
||||
params["recipe.url"] = url
|
||||
if category_ids is not None:
|
||||
params["recipe.recipeCategoryIdList"] = category_ids
|
||||
# For create: empty list just means no categories (don't send parameter).
|
||||
if len(category_ids) > 0:
|
||||
params["recipe.recipeCategoryIdList"] = category_ids
|
||||
return params
|
||||
|
||||
|
||||
@@ -225,5 +227,11 @@ def build_update_params(
|
||||
if url is not None:
|
||||
params["recipe.url"] = url
|
||||
if category_ids is not None:
|
||||
params["recipe.recipeCategoryIdList"] = category_ids
|
||||
# Empty list: send empty string to remove all categories.
|
||||
# Non-empty list: send as-is (httpx sends list values multiple times).
|
||||
# Verified via testing: empty string "" removes categories, empty list [] does not.
|
||||
if len(category_ids) == 0:
|
||||
params["recipe.recipeCategoryIdList"] = ""
|
||||
else:
|
||||
params["recipe.recipeCategoryIdList"] = category_ids
|
||||
return params
|
||||
|
||||
@@ -1699,6 +1699,27 @@ def like_post(post_id: str, like: bool = True) -> str:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _get_family_id() -> str:
|
||||
"""Extract the primary family ID from famlistfamily response.
|
||||
|
||||
Returns:
|
||||
The family ID as a string (e.g. "23431854").
|
||||
|
||||
Raises:
|
||||
RuntimeError: On credential or API errors.
|
||||
"""
|
||||
try:
|
||||
families = _famlistfamily()
|
||||
if families and isinstance(families, list) and len(families) > 0:
|
||||
primary = families[0]
|
||||
metaid = primary.get("metaId")
|
||||
if isinstance(metaid, str) and metaid.startswith("family/"):
|
||||
return metaid.split("/", 1)[1]
|
||||
except RuntimeError:
|
||||
pass
|
||||
raise RuntimeError("Could not determine family ID")
|
||||
|
||||
|
||||
def _get_raw_recipes() -> list[dict[str, Any]]:
|
||||
"""Login, call metasync with id='recipe', logout and return the raw recipe list.
|
||||
|
||||
@@ -1725,50 +1746,53 @@ def get_recipe_categories() -> str:
|
||||
"""Return all available recipe categories for the family.
|
||||
|
||||
Returns:
|
||||
JSON list of recipe category objects with keys: id, name, emoji.
|
||||
JSON list of category IDs (e.g. ["category/23431854_2", ...]).
|
||||
Always includes the 5 free-tier standard categories if family ID is available.
|
||||
Returns an error message string on failure.
|
||||
"""
|
||||
# Attempt to get categories via metasync call (similar to recipes).
|
||||
# If the endpoint supports it, use that. Otherwise fallback to extracting
|
||||
# categories from existing recipes.
|
||||
# Get the family ID to construct standard category IDs
|
||||
family_id: str | None = None
|
||||
try:
|
||||
data = _authenticated_call("metasync", {"id": "recipeCategory"})
|
||||
family_id = _get_family_id()
|
||||
except RuntimeError:
|
||||
# Endpoint doesn't exist or failed. Fall back to extracting from recipes.
|
||||
try:
|
||||
raw_recipes = _get_raw_recipes()
|
||||
except RuntimeError as exc:
|
||||
return f"Error: {exc}"
|
||||
pass
|
||||
|
||||
# Collect unique category IDs from all recipes.
|
||||
seen_ids: set[str] = set()
|
||||
category_ids: list[str] = []
|
||||
for recipe in raw_recipes:
|
||||
if not isinstance(recipe, dict):
|
||||
continue
|
||||
recipe_cat_ids = recipe.get("recipeCategoryIdList")
|
||||
if not isinstance(recipe_cat_ids, list):
|
||||
continue
|
||||
for cat_id in recipe_cat_ids:
|
||||
if isinstance(cat_id, str) and cat_id and cat_id not in seen_ids:
|
||||
seen_ids.add(cat_id)
|
||||
category_ids.append(cat_id)
|
||||
return json.dumps(category_ids, ensure_ascii=False, indent=2)
|
||||
# Collect all category IDs: standard categories + categories found in recipes
|
||||
seen_ids: set[str] = set()
|
||||
category_ids: list[str] = []
|
||||
|
||||
# If metasync succeeded, extract categories from response.
|
||||
# Add the 5 free-tier standard categories if we have the family ID
|
||||
if family_id:
|
||||
standard_cat_ids = [
|
||||
f"category/{family_id}_2", # Bei Kindern beliebt (KIDS_LOVE)
|
||||
f"category/{family_id}_3", # Wirklich einfach (EASY)
|
||||
f"category/{family_id}_4", # Nachspeisen (DESSERT)
|
||||
f"category/{family_id}_5", # Schmeckt toll (DELICIOUS)
|
||||
f"category/{family_id}_6", # Gemüse (VEGETABLES)
|
||||
]
|
||||
for cat_id in standard_cat_ids:
|
||||
if cat_id not in seen_ids:
|
||||
seen_ids.add(cat_id)
|
||||
category_ids.append(cat_id)
|
||||
|
||||
# Add any additional categories found in recipes (e.g., premium categories)
|
||||
try:
|
||||
items: list[dict[str, Any]] = data["a00"]["r"]["r"]["updatedCreated"]
|
||||
except (KeyError, TypeError):
|
||||
items = []
|
||||
raw_recipes = _get_raw_recipes()
|
||||
except RuntimeError:
|
||||
raw_recipes = []
|
||||
|
||||
if not isinstance(items, list):
|
||||
return json.dumps({"error": "No recipe categories available"}, ensure_ascii=False)
|
||||
for recipe in raw_recipes:
|
||||
if not isinstance(recipe, dict):
|
||||
continue
|
||||
recipe_cat_ids = recipe.get("recipeCategoryIdList")
|
||||
if not isinstance(recipe_cat_ids, list):
|
||||
continue
|
||||
for cat_id in recipe_cat_ids:
|
||||
if isinstance(cat_id, str) and cat_id and cat_id not in seen_ids:
|
||||
seen_ids.add(cat_id)
|
||||
category_ids.append(cat_id)
|
||||
|
||||
result: list[str] = []
|
||||
for cat_id in items:
|
||||
if isinstance(cat_id, str):
|
||||
result.append(cat_id)
|
||||
return json.dumps(result, ensure_ascii=False, indent=2)
|
||||
return json.dumps(category_ids, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user