Mealie stores the literal string "no image" in the recipe's image field when
there is no cover, so bool(recipe["image"]) reported a cover that is not
there: the check meant to catch a missing image downgraded to a warning
instead of failing. Observed on a recipe created through Mealie's own image
import, which returned image: "no image".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Recipes imported from a video caption have no nutrition data, so the values
have to be estimated. Mealie will accept anything: the fields are free-text
strings, a misspelt key is dropped silently, and a whole-recipe total looks
exactly like a per-serving one. The recipe page then renders whatever landed
as fact.
patch_recipe now validates a nutrition block first. Keys must be Mealie's own,
so a number cannot vanish into "carbs". Values are normalized to bare numbers,
matching how the library already stores them. Energy is checked against the
macros with the Atwater factors (4/9/4 kcal per gram) and refused if it is
more than 25% off, which is what catches an arithmetic slip.
Nutrition is per serving, and the text import left recipeServings at 0 --- it
set only the free-text recipeYield, so "4-6 personer" gave Mealie no number to
divide by or scale with. import_recipe_text now also sets recipeServings, from
the lower bound of a range, which is how this library already stores
"10-12 personer" (recipeServings 10). patch_recipe refuses nutrition while the
count is still missing, and accepts it when the same patch supplies it.
verify_recipe fails an import that has no nutrition, or has values without a
serving count.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two failures from today's TikTok import, both misread as faults on the
Mealie instance.
set_cover_image passed slug_or_id straight into /api/recipes/{slug}/image.
That path only resolves slugs: /api/recipes/{slug} itself accepts an id, the
routes beneath it do not. With the recipe's id the scrape POST answered 404
and the file upload PUT answered 500 — neither of which says "wrong key", so
the agent concluded the image endpoint was broken and gave up on the cover.
Verified on this instance (v3.22.0): GET /api/recipes/<id>/comments answers
500 while the same call with the slug answers 200. The recipe is now read
first and its stored slug used for the sub-route.
patch_recipe sent tags through untouched, so {"name": "Källa: TikTok"} went
out as-is and came back 422. RecipeTag and RecipeCategory both require name
*and* slug per the instance's OpenAPI schema. Tags and categories are now
resolved before the PATCH: an existing organizer is reused (case-insensitive
on name) so tagging cannot fork a vocabulary that already holds "Källa:
Instagram" and "Källa: YouTube", and a genuinely new name is created through
its own endpoint, which supplies the slug Mealie itself would pick.
Neither path had a test that could have caught this: the cover tests always
passed a slug, and the patch contract test asserted the 422-producing shape
as if it were correct.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
import_recipe_text posted plain Swedish text to
/api/recipes/create/html-or-json. That endpoint is a scraper entry point:
Mealie runs recipe_scrapers over whatever `data` holds and answers 400 for
anything without schema.org markup. A text block never had any, so the tool
returned 400 on every call it has ever made — including a minimal one-line
test recipe. The seven session logs of the importing agent contain no
successful text import; every recipe that landed came in through
import_recipe_url.
The existing test mocked _request away, so it asserted the payload shape and
never that Mealie would accept it. Green test, broken tool.
Create the recipe with POST /api/recipes and fill it with a PATCH instead.
Both are already exercised against this instance. Ingredients are stored as
readable `note` lines, which is what parse_ingredients expects to structure
afterwards.
Also accept a parenthetical on a section heading. `Gör så här (REKONSTRUERAD):`
marks where the steps came from, and the old exact-match check turned that
import away before it reached Mealie.
A PATCH that fails now leaves a name-only recipe, so the error names the slug
rather than leaving a silent empty stub.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Buzz's read-only Mealie agent cannot import recipes, and the full server
exposes delete_recipe and the Home Assistant shopping-list write to any
client that runs it. Neither profile fits an on-demand import agent.
Add IMPORT_TOOL_NAMES — the read-only allowlist plus import_recipe_url,
import_recipe_text, import_recipe_image, patch_recipe, parse_ingredients
and set_cover_image — and expose it through main_import(). Adding a recipe
is recoverable from the Mealie UI; removing one is not, so delete_recipe
stays out of the profile even though it writes.
Verified over stdio: the entrypoint lists exactly those 15 tools.
Co-authored-by: fredamn76 <fredrik.fallman@gmail.com>
Signed-off-by: fredamn76 <fredrik.fallman@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Remove every tool that is not explicitly approved for the private Buzz agent. This keeps future mutating tools closed by default instead of relying on a denylist that must be updated manually.
Co-authored-by: fredamn76 <fredrik.fallman@gmail.com>
Signed-off-by: fredamn76 <fredrik.fallman@gmail.com>
Expose the existing stdio server to a dedicated Buzz agent without granting mutation tools or leaking unrelated runtime credentials. Document the verified Hermes and Buzz client paths.
Co-authored-by: fredamn76 <fredrik.fallman@gmail.com>
Signed-off-by: fredamn76 <fredrik.fallman@gmail.com>
Mealie's POST /api/recipes/{slug}/image downloads the URL it is given and
rejects anything that is not an image, so handing it a recipe page answered
400 {"message": "Url is not an image"}. This was read as a source-specific
failure on ica.se, but that page's own og:image uploads fine (verified live,
200 + 74608 bytes of image/webp), and httpbin's JPEG works too — the endpoint
simply never accepted pages.
Callers naturally pass the recipe page they just imported from, so resolve a
page to its advertised og:image and retry rather than making every caller know
the distinction. A page with no discoverable image now raises instead of
leaving the recipe silently coverless.
Co-authored-by: fredamn76 <fredrik.fallman@gmail.com>
Signed-off-by: fredamn76 <fredrik.fallman@gmail.com>
suggest_recipes returned 422 for every call. The endpoint takes food UUIDs as
repeated query parameters, not a comma-joined string of names — the rule
inherited from the skill was never actually verified. Added resolve_foods to
map names to food records first, and report anything unresolved instead of
dropping it.
scale_ingredients silently returned unscaled lines. It read the structured
quantity field and string-replaced str(quantity) into the display text, which
failed twice over: unparsed recipes store quantity 0.0 with the real amount only
in the text, and str(800.0) never matches "800 g kycklinglårfilé". Amounts are
now read from the display text, ranges scale at both ends ("1-1,5 msk"), mixed
numbers scale as one value ("1 1/2 msk"), and lines with no amount are reported
in `unscaled` rather than presented as if they had been scaled.
looks_mangled flagged the readable "1 1/2 msk tomatpuré" because its duplicate-word
rule matched the repeated digit. Restricted to alphabetic words, so it still
catches "2 dl dl grädde".
Verified against all 238 recipes in the live library: no false positives remain.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ports a working Hermes agent skill to an MCP server. The skill's value was not
its Mealie endpoints but ~25 operational rules found by running imports against
the live instance; those are now code with tests rather than prompt text.
The server owns deterministic mechanics — auth, the non-default User-Agent
Cloudflare requires, the `extension` field on cover uploads, PATCH instead of
the 500-ing bulk-action endpoints, decimal-comma normalization, and restoring
Swedish display text after parsing. Language and taste judgement stay with the
model, fed by the four reference notes shipped as MCP resources.
verify_recipe runs the finished-import definition as code so an agent cannot
report success on a recipe with an English ingredient line, a missing cover, or
ingredients still in the "Click Parse" state.
Home Assistant is optional and isolated; the Obsidian meal plan is out of scope.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>